﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

#if NETFX_CORE
using Windows.ApplicationModel.Resources;

#endif

namespace StyleMVVM.Validation.Impl.Fluent
{
	public class FluentStringProperty<T> : IStringPropertyOperator<T>, IFluentPropertyValueProvider<T>
	{
		private enum StringModifier
		{
			StartsWith,
			EndsWith,
			IndexOf,
			LastIndexOf,
			Regex,
			Split,
			ToLower,
			ToUpper
		}

		private readonly string propertyName;
		private readonly IFluentRule<T> fluentRule;
		private StringModifier modifier;
		private string modifierString;
		private char[] trimChars;
		private char split;

		private static MethodInfo startsWithMethod;
		private static MethodInfo endsWithMethod;
		private static MethodInfo indexOfMethod;
		private static MethodInfo lasIndexOfMethod;
		private static MethodInfo regexMethod;
		private static MethodInfo splitMethod;
		private static MethodInfo toUpperWithMethod;
		private static MethodInfo toLowerWithMethod;

		static FluentStringProperty()
		{
			startsWithMethod = typeof(FluentStringProperty<object>).GetTypeInfo().GetDeclaredMethod("StartsWithObject");

			endsWithMethod = typeof(FluentStringProperty<object>).GetTypeInfo().GetDeclaredMethod("EndsWithObject");

			indexOfMethod = typeof(FluentStringProperty<object>).GetTypeInfo().GetDeclaredMethod("IndexOfObject");

			lasIndexOfMethod = typeof(FluentStringProperty<object>).GetTypeInfo().GetDeclaredMethod("LastIndexOfObject");

			regexMethod = typeof(FluentStringProperty<object>).GetTypeInfo().GetDeclaredMethod("RegexObject");

			toUpperWithMethod = typeof(FluentStringProperty<object>).GetTypeInfo().GetDeclaredMethod("ToUpperObject");

			toLowerWithMethod = typeof(FluentStringProperty<object>).GetTypeInfo().GetDeclaredMethod("ToLowerObject");
		}

		public FluentStringProperty(IFluentRule<T> fluentRule, string propertyName)
		{
			this.fluentRule = fluentRule;
			this.propertyName = propertyName;
		}

		public IFluentRule<T> StartsWith(string beginning)
		{
			modifierString = beginning;

			modifier = StringModifier.StartsWith;

			return fluentRule;
		}

		public IFluentRule<T> EndsWith(string ending)
		{
			modifierString = ending;

			modifier = StringModifier.EndsWith;

			return fluentRule;
		}

		public IFluentRule<T> IndexOf(string s)
		{
			modifierString = s;

			modifier = StringModifier.IndexOf;

			return fluentRule;
		}

		public IFluentRule<T> LastIndexOf(string s)
		{
			modifierString = s;

			modifier = StringModifier.LastIndexOf;

			return fluentRule;
		}

		public IFluentRule<T> Regex(string regexString)
		{
			modifierString = regexString;

			modifier = StringModifier.Regex;

			return fluentRule;
		}

		public IFluentRule<T> Split(char splitChar)
		{
			split = splitChar;

			modifier = StringModifier.Split;

			return fluentRule;
		}

		public IFluentRule<T> ToLower()
		{
			modifier = StringModifier.ToLower;

			return fluentRule;
		}

		public IFluentRule<T> ToUpper()
		{
			modifier = StringModifier.ToUpper;

			return fluentRule;
		}

		public string PropertyName
		{
			get { return propertyName; }
		}

		public Expression GenerateExpression(IFluentExpressionProvider propertyExpressionProvider)
		{
			Expression returnValue = null;

			switch (modifier)
			{
				case StringModifier.StartsWith:
					returnValue = Expression.Call(startsWithMethod,
					                              propertyExpressionProvider.GetPropertyExpression(propertyName),
					                              Expression.Constant(modifierString));
					break;

				case StringModifier.EndsWith:
					returnValue = Expression.Call(endsWithMethod,
					                              propertyExpressionProvider.GetPropertyExpression(propertyName),
					                              Expression.Constant(modifierString));
					break;

				case StringModifier.IndexOf:
					returnValue = Expression.Call(indexOfMethod,
					                              propertyExpressionProvider.GetPropertyExpression(propertyName),
					                              Expression.Constant(modifierString));
					break;

				case StringModifier.LastIndexOf:
					returnValue = Expression.Call(lasIndexOfMethod,
					                              propertyExpressionProvider.GetPropertyExpression(propertyName),
					                              Expression.Constant(modifierString));
					break;

				case StringModifier.Regex:
					returnValue = Expression.Call(regexMethod,
					                              propertyExpressionProvider.GetPropertyExpression(propertyName),
					                              Expression.Constant(modifierString));
					break;

				case StringModifier.Split:
					returnValue = Expression.Call(splitMethod,
					                              propertyExpressionProvider.GetPropertyExpression(propertyName),
					                              Expression.Constant(split));
					break;

				case StringModifier.ToLower:
					returnValue = Expression.Call(toLowerWithMethod,
					                              propertyExpressionProvider.GetPropertyExpression(propertyName));
					break;

				case StringModifier.ToUpper:
					returnValue = Expression.Call(toUpperWithMethod,
					                              propertyExpressionProvider.GetPropertyExpression(propertyName));
					break;
			}

			return returnValue;
		}

		public string GenerateDisplayString(ResourceLoader loader)
		{
			string returnValue = null;
#if NETFX_CORE
			string formatString = null;

			switch (modifier)
			{
				case StringModifier.StartsWith:
					formatString = loader.GetString("StartsWith");

					returnValue = string.Format(formatString, propertyName, modifierString);
					break;

				case StringModifier.EndsWith:
					formatString = loader.GetString("EndsWith");

					returnValue = string.Format(formatString, propertyName, modifierString);
					break;

				case StringModifier.IndexOf:
					formatString = loader.GetString("IndexOf");

					returnValue = string.Format(formatString, propertyName, modifierString);
					break;

				case StringModifier.LastIndexOf:
					formatString = loader.GetString("LastIndexOf");

					returnValue = string.Format(formatString, propertyName, modifierString);
					break;

				case StringModifier.Regex:
					formatString = loader.GetString("Regex");

					returnValue = string.Format(formatString, propertyName, modifierString);
					break;

				case StringModifier.Split:
					formatString = loader.GetString("Split");

					returnValue = string.Format(formatString, propertyName, split);
					break;

				case StringModifier.ToLower:
					formatString = loader.GetString("ToLower");

					returnValue = string.Format(formatString, propertyName);
					break;

				case StringModifier.ToUpper:
					formatString = loader.GetString("ToUpper");

					returnValue = string.Format(formatString, propertyName);
					break;
			}
#else
			switch (modifier)
			{
				case StringModifier.StartsWith:
					returnValue = string.Format(FluentResources.StartsWith, propertyName, modifierString);
					break;
				case StringModifier.EndsWith:
					returnValue = string.Format(FluentResources.EndsWith, propertyName, modifierString);
					break;
				case StringModifier.IndexOf:
					returnValue = string.Format(FluentResources.IndexOf, propertyName, modifierString);
					break;
				case StringModifier.LastIndexOf:
					returnValue = string.Format(FluentResources.LastIndexOf, propertyName, modifierString);
					break;
				case StringModifier.Regex:
					returnValue = string.Format(FluentResources.Regex, propertyName, modifierString);
					break;
				case StringModifier.Split:
					returnValue = string.Format(FluentResources.Split, propertyName, split);
					break;
				case StringModifier.ToLower:
					returnValue = string.Format(FluentResources.ToLower, propertyName);
					break;
				case StringModifier.ToUpper:
					returnValue = string.Format(FluentResources.ToUpper, propertyName);
					break;
			}
#endif

			return returnValue;
		}

		private static object StartsWithObject(object target, string beginning)
		{
			string targetString = target as string;

			if (targetString != null)
			{
				return targetString.StartsWith(beginning, StringComparison.CurrentCultureIgnoreCase);
			}

			return false;
		}

		private static object EndsWithObject(object target, string beginning)
		{
			string targetString = target as string;

			if (targetString != null)
			{
				return targetString.EndsWith(beginning, StringComparison.CurrentCultureIgnoreCase);
			}

			return false;
		}

		private static object IndexOfObject(object target, string beginning)
		{
			string targetString = target as string;

			if (targetString != null)
			{
				return targetString.IndexOf(beginning, StringComparison.CurrentCultureIgnoreCase);
			}

			return 0;
		}

		private static object LastIndexOfObject(object target, string beginning)
		{
			string targetString = target as string;

			if (targetString != null)
			{
				return targetString.LastIndexOf(beginning, StringComparison.CurrentCultureIgnoreCase);
			}

			return 0;
		}

		private static object RegexObject(object target, string beginning)
		{
			string targetString = target as string;

			if (targetString != null)
			{
				return System.Text.RegularExpressions.Regex.IsMatch(targetString, beginning);
			}

			return false;
		}

		private static object SplitObject(object target, char split)
		{
			string targetString = target as string;

			if (targetString != null)
			{
				return targetString.Split(split);
			}

			return null;
		}

		private static object ToUpperObject(object target)
		{
			string targetString = target as string;

			if (targetString != null)
			{
				return targetString.ToUpper();
			}

			return null;
		}

		private static object ToLowerObject(object target)
		{
			string targetString = target as string;

			if (targetString != null)
			{
				return targetString.ToLower();
			}

			return null;
		}
	}
}